/*
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.als;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.core.IndirectHint;
import com.onegravity.sudoku.solver.core.Rule;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.data.Region;
import com.onegravity.sudoku.solver.util.BitSetIterator;
import com.onegravity.sudoku.solver.util.HtmlLoader;

/**
 * Almost Locked Candidates
 * @author Emanuel Moecklin
 */
public class ALCHint extends IndirectHint implements Rule {

	private static final long serialVersionUID = -3087062061987696929L;
	
	private final int degree;
	private final Set<Cell> als1Cells;
    private final Set<Cell> als2Cells;
    private final Region als1Region;
    private final Region als2Region;
    private final BitSet alsCandidates;

    private Map<Cell, BitSet> selectedPotentials;
    
	public ALCHint(int degree, ALS als1, ALS als2, Map<Cell, BitSet> removablePotentials) {
        super(removablePotentials);
        this.degree = degree;
        this.als1Cells = als1.getCells();
        this.als2Cells = als2.getCells();
        this.alsCandidates = als1.getPotentials();
        this.als1Region = als1.getRegion();
        this.als2Region = als2.getRegion();
        
        selectedPotentials = new HashMap<Cell, BitSet>();
        for (Cell cell:als1Region.getCellSet()) {
        	if (!removablePotentials.containsKey(cell)) {
            	BitSet tmp = (BitSet) cell.getPotentialValues().clone();
            	tmp.and(alsCandidates);
            	selectedPotentials.put(cell, tmp);
        	}
        }
        for (Cell cell:als2Cells) {
            selectedPotentials.put(cell, cell.getPotentialValues());
        }
    }

    @Override
    public Region[] getRegions(HintType hintType) {
    	if (hintType==HintType.SMALL_CLUE) return null;
        return new Region[] {als1Region, als2Region};
    }

    @Override
    public Collection<Cell> getSelectedCells(HintType hintType) {
    	if (hintType==HintType.SMALL_CLUE) {
			ArrayList<Cell> cells = new ArrayList<Cell>();
			cells.addAll(als1Cells);
			cells.addAll(als2Cells);
			return cells;
		}
		return Collections.emptyList();
    }

    @Override
    public Map<Cell, BitSet> getSelectedPotentials(HintType hintType, int viewNum) {
    	return (hintType!=HintType.SMALL_CLUE) ? selectedPotentials : null;
    }

    @Override
    public Map<ImmutableCell, BitSet> getRemovedPotentials(HintType hintType) {
        return (hintType==HintType.HINT) ? super.getRemovedPotentials(hintType) : null;
    }

    @Override
    public double getDifficulty() {
    	return (degree==2) ? 4.5 :
    		   (degree==3) ? 5.2 : 5.8;
    }

    @Override
    public String getName() {
    	return (degree==2) ? HintResource.ALMOST_LOCKED_CANDIDATES_2.getName(degree) :
    						 HintResource.ALMOST_LOCKED_CANDIDATES_N.getName(degree);
    }

    @Override
    public Uri getLink() {
    	return (degree==2) ? HintResource.ALMOST_LOCKED_CANDIDATES_2.getUrl() :
			 				 HintResource.ALMOST_LOCKED_CANDIDATES_N.getUrl();
	}

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof ALCHint)) return false;
        ALCHint other = (ALCHint)o;
        if (!this.als1Cells.equals(other.als1Cells)) return false;
        if (!this.als2Cells.equals(other.als2Cells)) return false;
        if (!this.als1Region.equals(other.als1Region)) return false;
        if (!this.als2Region.equals(other.als2Region)) return false;
        if (!this.alsCandidates.equals(other.alsCandidates)) return false;
        return true;
    }

    @Override
    public int hashCode() {
    	int hash = alsCandidates.hashCode();
   		hash ^= als1Cells.hashCode();
   		hash ^= als2Cells.hashCode();
   		hash ^= als1Region.hashCode();
   		hash ^= als2Region.hashCode();
        return hash;
    }

    @Override
    public String getHintHtml(HintType hintType) {
        if (degree==2) {
        	int value1 = alsCandidates.nextSetBit(0);
        	int value2 = alsCandidates.nextSetBit(value1+1);
            String result = HtmlLoader.loadHtml(HintResource.ALMOST_LOCKED_CANDIDATES_2.hint(hintType));
            return HtmlLoader.format(result, Cell.toString(als1Cells), Cell.toString(als2Cells), value1, value2, als1Region.toFullString(), als2Region.toFullString());
        }
        else {
        	StringBuffer alsCandValues = new StringBuffer();
        	boolean isFirst = true;
            for (BitSetIterator<Integer> it = new BitSetIterator<Integer>(alsCandidates); it.hasNext(); isFirst=false) {
            	alsCandValues.append( (isFirst ? "" : ", ") + it.next() ); //$NON-NLS-1$ //$NON-NLS-2$
            }
            int degreeRes = (degree==2) ? R.string.hint_pair : (degree==3) ? R.string.hint_triple : R.string.hint_quad;
            String result = HtmlLoader.loadHtml(HintResource.ALMOST_LOCKED_CANDIDATES_N.hint(hintType));
            return HtmlLoader.format(result, Cell.toString(als1Cells), Cell.toString(als2Cells), alsCandValues,
            		als1Region.toFullString(), als2Region.toFullString(), HintResource.getString(degreeRes),
            		als1Cells.size(), alsCandidates.cardinality());
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(getName());
        builder.append(": "); //$NON-NLS-1$
        builder.append(als1Cells.toString());
        builder.append(als2Cells.toString());
        builder.append(als1Region.toString());
        builder.append(als2Region.toString());
        builder.append(" on values " + alsCandidates.toString()); //$NON-NLS-1$
        return builder.toString();
    }

}